1 module modular_db.actions; 2 3 import std.exception: enforce; 4 5 import modular_db.database; 6 import modular_db.exceptions; 7 import modular_db.module_; 8 import modular_db.module_qualification; 9 10 private @system: 11 12 public enum Mode: ubyte { 13 load, 14 setup, 15 migrate, 16 } 17 18 ResultRange _queryModuleInfo(Database db, string sql, string moduleUrl) { 19 try 20 return db.execute(sql, moduleUrl); 21 catch (SqliteException) 22 throw new UninitializedDbException; 23 } 24 25 public long getModuleId(Database db, ModuleQualification q, string moduleUrl) { 26 auto moduleInfo = _queryModuleInfo(db, q.format!` 27 SELECT oid 28 FROM [-|0modules] 29 WHERE url = ? 30 `, moduleUrl); 31 enforce(!moduleInfo.empty, new ModuleNotFoundException(moduleUrl)); 32 return moduleInfo.oneValue!long; 33 } 34 35 auto _loadModule(L)(Database db, ref L loader, Mode mode, ModuleQualification q) { 36 const moduleUrl = loader.url; 37 const moduleVersion = loader.version_; 38 39 auto transaction = db.startTransaction(); 40 scope(success) transaction.commit(); 41 auto moduleInfo = _queryModuleInfo(db, q.format!` 42 SELECT oid, version 43 FROM [-|0modules] 44 WHERE url = ? 45 `, moduleUrl); 46 if (moduleInfo.empty) { 47 // No such module yet. 48 enforce(mode != Mode.load, new ModuleNotFoundException(moduleUrl)); 49 db.execute(q.format!` 50 INSERT INTO [-|0modules](url, version) 51 VALUES (?, ?) 52 `, moduleUrl, moduleVersion); 53 q._id = db.lastInsertRowid; 54 return loader.setup(db, q); 55 } 56 // Module exists. 57 const moduleId = moduleInfo.front.peek!long(0); 58 const storedVersion = moduleInfo.front.peek!long(1); 59 moduleInfo = typeof(moduleInfo).init; 60 if (storedVersion == moduleVersion) { 61 q._id = moduleId; 62 return loader.load(db, q); 63 } 64 // Needs migration. 65 enforce(mode == Mode.migrate && storedVersion < moduleVersion, 66 new InvalidModuleVersionException(moduleUrl, moduleVersion, storedVersion), 67 ); 68 db.execute(q.format!` 69 UPDATE [-|0modules] 70 SET version = ? 71 WHERE oid = ? 72 `, moduleVersion, moduleId); 73 q._id = moduleId; 74 return loader.migrate(db, q, storedVersion); 75 } 76 77 // We do not constrain these functions with `isModuleLoader!L`, so that we get better error 78 // messages. 79 public ModuleLoaderModuleType!L loadModule(L)( 80 Database db, ref L loader, Mode mode = Mode.load, string schema = "main", 81 ) { 82 return _loadModule(db, loader, mode, ModuleQualification(schema, 0L)); 83 } 84 85 public ModuleLoaderModuleType!L loadModule(L)( 86 Database db, Mode mode = Mode.load, string schema = "main", 87 ) { 88 L loader; 89 return loadModule(db, loader, mode, schema); 90 } 91 92 public void initialize(Database db, Mode mode = Mode.load, string schema = "main") { 93 import modular_db.module_module; 94 95 const q = ModuleQualification(schema, 0L); 96 ModuleModuleLoader loader; 97 if (mode == Mode.load) 98 _loadModule(db, loader, mode, q); 99 else 100 try 101 _loadModule(db, loader, mode, q); 102 catch (UninitializedDbException) { 103 auto transaction = db.startTransaction(); 104 scope(success) transaction.commit(); 105 loader.setup(db, q); 106 } 107 } 108 109 public void dropModule(Database db, string moduleUrl, string schema = "main") { 110 import std.algorithm.iteration: map; 111 import std.array: appender, array; 112 import std.conv: toChars; 113 import std.format: sformat; 114 import std.typecons: tuple; 115 116 import d2sqlite3.results: PeekMode; 117 118 const q = ModuleQualification(schema, 0L); 119 const moduleId = getModuleId(db, q, moduleUrl); 120 121 const nested = db.inTransaction; 122 const withForeignKeys = db.execute("PRAGMA foreign_keys").oneValue!bool; 123 if (withForeignKeys) { 124 enforce!DbException(!nested, 125 "Cannot drop a module in a transaction with enabled `foreign_keys`", 126 ); 127 db.execute("PRAGMA foreign_keys = OFF"); 128 } 129 scope(exit) 130 if (withForeignKeys) 131 db.execute("PRAGMA foreign_keys = ON"); 132 auto transaction = Transaction(db.raw, nested); 133 scope(success) transaction.commit(); 134 135 db.execute(q.format!` 136 DELETE FROM [-|0modules] 137 WHERE oid = ? 138 `, moduleId); 139 140 char[long.min.toChars().length + 7] buffer = void; 141 auto name = appender!(char[ ]); 142 name.reserve(32); 143 foreach (entity; 144 db.execute( 145 q.format!` 146 SELECT type, name 147 FROM [-|sqlite_master] 148 WHERE name GLOB ? 149 `, 150 buffer[ ].sformat!"%s[^0-9]*"(moduleId), 151 ).map!((row) { 152 name.clear(); 153 foreach (c; row.peek!(string, PeekMode.slice)(1)) 154 if (c != '"') 155 name ~= c; 156 else 157 name ~= `""`; 158 return tuple!(q{type}, q{name})(row.peek!string(0), name.data.idup); 159 }).array() 160 ) 161 db.execute(q.format!`DROP %1$s [-|%2$s]`(entity.type, entity.name)); 162 }